package mobilefeel.sdk;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;

import mobilefeel.dto.ScreenCoordinates;
import mobilefeel.dto.TouchEvent;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;

/**
 * MobileFeelActivity is an Android Activity that allows the application to
 * connect and send events to the desktop server application implemented with
 * the MobileFeel SDK. Every Activity in your Android application that either:
 * needs to connect to the PC application, contains views that are required to 
 * fire tactile events to the PC application, needs to send and/or receive data 
 * from the PC application, etc. must extend MobileFeelActivity instead of
 * Android's Activity.
 * <br/><br/>
 * MobileFeelActivity is also in charge of controlling the Android
 * service that manages the connection and events between the Android device
 * and the PC application. MobileFeelActivity actually extends Android's 
 * Activity, so all the behaviour of a normal Android Activity is also
 * available.
 * <br/><br/>
 * Any Android application containing one or more MobileFeelActivity must
 * declare a reference to MobileFeelService and have INTERNET permission in its
 * Manifest file.
 * <br/><br/>
 * {@code <application ....>}<br/>
 * {@code	<activity ...>}<br/>
 * {@code		...}<br/>
 * {@code 	</activity>}<br/>
 * {@code		...}<br/>
 * {@code	<service android:name="mobilefeel.sdk.MobileFeelService" />}<br/>
 * {@code </application>}<br/>
 * {@code <uses-permission android:name="android.permission.INTERNET"/>}
 */
public abstract class MobileFeelActivity extends Activity implements OnClickListener, OnLongClickListener {

	private Intent intent;
	
	private ArrayList<Integer> onClick; 
	private ArrayList<Integer> onLongClick; 
	private ArrayList<Integer> onTouch; 
	private HashMap<Integer, int[]> onTouchPointers;
	
	private MobileFeelBroadcastReceiver broadcastReceiver;
	static final String CONNECTION_ESTABLISHED = "mobilefeel.sdk.CONNECTION_ESTABLISHED";
	static final String CONNECTION_FAILED = "mobilefeel.sdk.CONNECTION_FAILED";
	static final String CUSTOM_DATA = "mobilefeel.sdk.CUSTOM_DATA";
	static final String DISCONNECTION = "mobilefeel.sdk.DISCONNECTION";
	static final String REGISTER_CLICK_EVENT = "mobilefeel.sdk.REGISTER_CLICK_EVENT";
	static final String UNREGISTER_CLICK_EVENT = "mobilefeel.sdk.UNREGISTER_CLICK_EVENT";
	static final String REGISTER_LONGCLICK_EVENT = "mobilefeel.sdk.REGISTER_LONGCLICK_EVENT";
	static final String UNREGISTER_LONGCLICK_EVENT = "mobilefeel.sdk.UNREGISTER_LONGCLICK_EVENT";
	static final String REGISTER_TOUCH_EVENT = "mobilefeel.sdk.REGISTER_TOUCH_EVENT";
	static final String UNREGISTER_TOUCH_EVENT = "mobilefeel.sdk.UNREGISTER_TOUCH_EVENT";
	static final String UNREGISTER_ALL_EVENTS = "mobilefeel.sdk.UNREGISTER_ALL_EVENTS";
	
	static final String VIEW_ID = "mobilefeel.sdk.VIEW_ID";
	static final String CONNECTION_FAILED_REASON = "mobilefeel.sdk.CONNECTION_FAILED_REASON";
	static final String TEXT = "mobilefeel.sdk.TEXT";
	static final String OBJECT = "mobilefeel.sdk.OBJECT";
	
	static final String ON_CLICK = "mobilefeel.sdk.ON_CLICK";
	static final String ON_LONG_CLICK = "mobilefeel.sdk.ON_LONG_CLICK";
	static final String ON_TOUCH = "mobilefeel.sdk.ON_TOUCH";
	
    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		onClick = new ArrayList<Integer>();
		onLongClick = new ArrayList<Integer>();
		onTouch = new ArrayList<Integer>();
		onTouchPointers = new HashMap<Integer, int[]>();
		
		broadcastReceiver = new MobileFeelBroadcastReceiver();
		IntentFilter intentFilter = new IntentFilter();
		intentFilter.addAction(CONNECTION_ESTABLISHED);
		intentFilter.addAction(CONNECTION_FAILED);
		intentFilter.addAction(REGISTER_CLICK_EVENT);
		intentFilter.addAction(UNREGISTER_CLICK_EVENT);
		intentFilter.addAction(REGISTER_LONGCLICK_EVENT);
		intentFilter.addAction(UNREGISTER_LONGCLICK_EVENT);
		intentFilter.addAction(REGISTER_TOUCH_EVENT);
		intentFilter.addAction(UNREGISTER_TOUCH_EVENT);
		intentFilter.addAction(UNREGISTER_ALL_EVENTS);
		intentFilter.addAction(CUSTOM_DATA);
		intentFilter.addAction(DISCONNECTION);
		registerReceiver(broadcastReceiver, intentFilter);
		
        intent = new Intent(this, MobileFeelService.class);
        startService(intent);
	}
	
	/**
	 * Connects the Android device to the server application running the
	 * desktop API, specifying the server's IP address and port. Timeout will
	 * be set by default to 5000 milliseconds.<br/>The server must already be
	 * running and listening for incoming connections at the specified port.
	 * After the connection is established successfully, the
	 * {@link MobileFeelActivity#onConnectionEstablished()} callback will be
	 * called, while {@link MobileFeelActivity#onConnectionFailed(String)} will
	 * be called if connection fails. A successful connection will trigger an
	 * {@link DeviceListener#onDeviceConnected()} to be fired on the desktop
	 * application.
	 * 
	 * @param ip the server's remote address (cannot be null)
	 * @param port the port number the server is listening for connection
	 * requests on
	 */
	protected final void connect(String ip, int port) {
		if (ip == null)
			throw new IllegalArgumentException("ip cannot be null.");
		connect(ip, port, 5000);
	}
	
	/**
	 * Connects the Android device to the server application running the
	 * desktop API, specifying the server's IP address, port and timeout. A
	 * timeout of zero is interpreted as an infinite timeout. The connection
	 * will then block until established or an error occurs.<br/>The server
	 * must already be running and listening for incoming connections at the
	 * specified port. After the connection is established successfully, the
	 * {@link MobileFeelActivity#onConnectionEstablished()} callback will be
	 * called, while {@link MobileFeelActivity#onConnectionFailed(String)} will
	 * be called if connection fails. A successful connection will trigger an
	 * {@link DeviceListener#onDeviceConnected()} to be fired on the desktop
	 * application.
	 * 
	 * @param ip the server's remote address (cannot be null)
	 * @param port the port number the server is listening for connection
	 * requests on
	 * @param timeout the desired timeout (in milliseconds)
	 */
	protected final void connect(String ip, int port, int timeout){
		if (ip == null)
			throw new IllegalArgumentException("ip cannot be null.");
        Intent i = this.intent.setAction(MobileFeelService.CONNECT);
        i.putExtra(MobileFeelService.IP, ip);
        i.putExtra(MobileFeelService.PORT, port);
        i.putExtra(MobileFeelService.TIMEOUT, timeout);
        startService(i);
	}
    
	/**
	 * Ends the connection between the Android device and the desktop server
	 * application. No further communication or events will be possible until
	 * reconnection. Calling this method fires an
	 * {@link MobileFeelActivity#onDisconnection(boolean)} event, with the
	 * remote parameter set to false. A disconnection will trigger an
	 * {@link DeviceListener#onDeviceDisconnected()} to be fired on the desktop
	 * application.
	 */
	protected final void disconnect() {
		Intent i = this.intent.setAction(MobileFeelService.DISCONNECT);
		startService(i);
		onDisconnection(false);
	}
	
	/**
	 * Registers an Android Activity's view, allowing developers to request
	 * tactile events from the view from within the PC application. The view
	 * must be an existent view (e.g. make sure your layout was inflated before
	 * calling this method). A registered view will trigger an
	 * {@link ViewListener#onViewRegistered()} to be fired on the desktop
	 * application. If the same view is registered several times, its
	 * parameters will be updated.
	 * <br/><br/>
	 * If you register programmatically-created views, make sure they contain
	 * an unique ID. Also, when registering a view, width and height
	 * information will be sent to the PC application. For the view to contain
	 * valid size information, this method must be called after the layout is
	 * applied and the Activity shown to the user. The
	 * {@link MobileFeelActivity#onWindowFocusChanged(boolean)} method is
	 * usually a good place to call this method.
	 * 
	 * @param view the Android view to register (cannot be null)
	 * @param name a programmer-defined String to identify the view being sent
	 */
	protected final void registerView(View view, String name) {
		if (view == null)
			throw new IllegalArgumentException("view cannot be null.");
		registerView(view.getId(), name);
	}
	
	/**
	 * Registers an Android Activity's view, allowing developers to request
	 * tactile events from the view from within the PC application. The view
	 * must be an existent view (e.g. make sure your layout was inflated before
	 * calling this method). A registered view will trigger an
	 * {@link ViewListener#onViewRegistered()} to be fired on the desktop
	 * application. If the same view is registered several times, its
	 * parameters will be updated.
	 * <br/><br/>
	 * If you register programmatically-created views, make sure they contain
	 * an unique ID. Also, when registering a view, width and height
	 * information will be sent to the PC application. For the view to contain
	 * valid size information, this method must be called after the layout is
	 * applied and the Activity shown to the user. The
	 * {@link MobileFeelActivity#onWindowFocusChanged(boolean)} method is
	 * usually a good place to call this method.
	 * 
	 * @param id the ID of the Android view to register
	 * @param name a programmer-defined String to identify the view being sent
	 */
	protected final void registerView(int id, String name) {
		int width = 0, height = 0;
		View view = findViewById(id);
		if (view != null) {
			width = view.getWidth();
			height = view.getHeight();
		}
		
		Intent i = this.intent.setAction(MobileFeelService.REGISTER_VIEW);
		i.putExtra(MobileFeelService.ID, id);
		i.putExtra(MobileFeelService.NAME, name);
		i.putExtra(MobileFeelService.WIDTH, width);
		i.putExtra(MobileFeelService.HEIGHT, height);
		startService(i);
	}
	
    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
	@Override
    protected void onResume() {
        super.onResume();
         
        Intent i = this.intent.setAction(MobileFeelService.ON_RESUME);
        startService(i);
	}

    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
	@Override
    protected void onPause() {
        super.onPause();
        
        Intent i = this.intent.setAction(MobileFeelService.ON_PAUSE);
        startService(i);
    }
	
    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
	protected void onDestroy() {
		if (isFinishing())
			onDestroy(true);
		else
			onDestroy(false);
	}
	
    /**
     * This method allows you to decide whether the MobileFeelService (and the
     * connection to the desktop server application together with it) must stop
     * running when the current Activity is destroyed. Activity's {@link
     * MobileFeelActivity#onDestroy()} will be called by this method. Call this
     * method when overriding your Activity's {@link 
     * MobileFeelActivity#onDestroy()}. Unless you explicitly call this method
     * with its parameter set to <i>false</i>, the service will be stopped when
     * the Activity is destroyed, except during configuration changes
     * (orientation change, physical keyboard opened or closed, etc.)
     * 
     * @param stopService whether the service should be stopped when the
     * Activity is destroyed
     */
    protected final void onDestroy(boolean stopService) {
        super.onDestroy();
     
        unregisterReceiver(broadcastReceiver);
        
        if (stopService) {
        	Log.d("MobileFeel", "Stopping service");
        	stopService(this.intent);
        }
    }
    
    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
    protected void onSaveInstanceState(Bundle outState) {
    	outState.putIntegerArrayList(ON_CLICK, this.onClick);
    	outState.putIntegerArrayList(ON_LONG_CLICK, this.onLongClick);
    	outState.putIntegerArrayList(ON_TOUCH, this.onTouch);
    	super.onSaveInstanceState(outState);
    }
    
    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
    	super.onRestoreInstanceState(savedInstanceState);
    	
    	if(savedInstanceState != null) {
    		if(savedInstanceState.containsKey(ON_CLICK))
    			onClick = savedInstanceState.getIntegerArrayList(ON_CLICK);
    		if(savedInstanceState.containsKey(ON_LONG_CLICK))
    			onLongClick = savedInstanceState.getIntegerArrayList(ON_LONG_CLICK);
    		if(savedInstanceState.containsKey(ON_TOUCH))
    			onTouch = savedInstanceState.getIntegerArrayList(ON_TOUCH);
    		
    		for(int id : onClick){
    			View view = this.findViewById(id);
    			if(view != null)
    				view.setOnClickListener(this);
    		}

    		for(int id : onLongClick){
    			View view = this.findViewById(id);
    			if(view != null)
    				view.setOnLongClickListener(this);
    		}
    	}
    }
    
    /**
     * Called by the API when the connection to the desktop server application
     * is established successfully. Override it in your Activity to be able to
     * react to a successful connection. This is usually a good place to switch
     * to another Activity, hide the connection form, etc. This method has no
     * default implementation.
     */
    protected void onConnectionEstablished() {}

    /**
     * Called when the connection to the desktop server application failed
     * because the connection timed out, the host could not be found, access
     * was denied, or other reasons. Override it in your Activity to be able to
     * react to unsuccessful connection attempts. This method has no default
     * implementation.
     * 
     * @param reason a String containing the reason behind the connection
     * failure
     */
    protected void onConnectionFailed(String reason) {}
    
    /**
	 * Called when the desktop server application sends Custom Data
	 * (Serilizable objects) to the Android device. Override it in your
	 * Activity to be able to receive Serializable objects from your PC
	 * application. The default implementation outputs the received object's
	 * {@link Object#toString()} to the device's Log.
     * 
     * @param text a String defining the type of information being sent
     * @param object the Serilizable object
     */
    protected void onCustomDataReceived(String text, Serializable object) {
    	String objString = "null";
    	if (object != null)
    		objString = object.toString();
    	Log.i("MobileFeel", "Custom Data Received: " + text + " - " + objString);
    }
    
    /**
     * Called when the connection between the Android device and the desktop
     * server application ends. Override it in your Activity to be able to
     * react to a disconnection. This method has no default implementation.
     * 
     * @param remote whether the disconnection was local (false) or done from
     * the desktop server application (true)
     */
    protected void onDisconnection(boolean remote) {}

    private class MobileFeelBroadcastReceiver extends BroadcastReceiver {
    	@Override
		public void onReceive(Context context, Intent intent) {
    		View view = null;
    		if (intent.hasExtra(VIEW_ID))
    			view = findViewById(intent.getIntExtra(VIEW_ID, 0));
    		
			if (intent.getAction().equals(CONNECTION_ESTABLISHED))
				onConnectionEstablished();
			else if (intent.getAction().equals(CONNECTION_FAILED)){
				onConnectionFailed(intent.getStringExtra(CONNECTION_FAILED_REASON));
			}else if (intent.getAction().equals(REGISTER_CLICK_EVENT) && view != null){
				view.setOnClickListener(MobileFeelActivity.this);
				if (!onClick.contains((Integer)view.getId()))
						onClick.add((Integer)view.getId());
	    	}else if (intent.getAction().equals(REGISTER_LONGCLICK_EVENT) && view != null){
				view.setOnLongClickListener(MobileFeelActivity.this);
				if (!onLongClick.contains((Integer)view.getId()))
					onLongClick.add((Integer)view.getId());
	    	}else if (intent.getAction().equals(REGISTER_TOUCH_EVENT) && view != null){
				if (!onTouch.contains((Integer)view.getId()))
					onTouch.add((Integer)view.getId());
			}else if (intent.getAction().equals(UNREGISTER_CLICK_EVENT) && view != null){
				view.setOnClickListener(null);
				onClick.remove((Integer)view.getId());
			}else if (intent.getAction().equals(UNREGISTER_LONGCLICK_EVENT) && view != null){
				view.setOnLongClickListener(null);
				onLongClick.remove((Integer)view.getId());
			}else if (intent.getAction().equals(UNREGISTER_TOUCH_EVENT) && view != null){
				onTouch.remove((Integer)view.getId());
			}else if (intent.getAction().equals(UNREGISTER_ALL_EVENTS)) {
				unregisterAllEvents();
			}else if (intent.getAction().equals(CUSTOM_DATA)) {
				onCustomDataReceived(intent.getStringExtra(TEXT), intent.getSerializableExtra(OBJECT));
			}else if (intent.getAction().equals(DISCONNECTION)) {
				onDisconnection(true);
			}
    	}
    }

    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    	boolean defaultImplementation = true;
    	
    	int action = ev.getAction() & MotionEvent.ACTION_MASK;
    	int actionPointer = -1;
    	if (action == MotionEvent.ACTION_POINTER_DOWN || action == MotionEvent.ACTION_POINTER_UP)
    		actionPointer = ev.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
    	
    	for (int id : onTouch) {
    		View view = findViewById(id);
    		if (view != null) {
    			boolean[] pointerContains = new boolean[ev.getPointerCount()];
    			boolean[] newTouchPointers = new boolean[ev.getPointerCount()];
    			Rect rect = new Rect();
				view.getGlobalVisibleRect(rect);
    			for (int p = 0; p < ev.getPointerCount(); p++) {
    				pointerContains[p] = rect.contains((int)ev.getX(p), (int)ev.getY(p));
    				newTouchPointers[p] = pointerContains[p];
    			}
    			
    			TouchEvent touchEvent = new TouchEvent();
    			touchEvent.setActionPointer(-1);
    			switch (action) {
    				case MotionEvent.ACTION_DOWN:
    					if (!pointerContains[0])
    						continue;
    					touchEvent.setAction(TouchEvent.ACTION_DOWN);
    					break;
    				case MotionEvent.ACTION_UP:
    					if (!hasTouchPointers(id))
    						continue;
    					touchEvent.setAction(TouchEvent.ACTION_UP);
    					newTouchPointers[0] = false;
    					break;
    				case MotionEvent.ACTION_MOVE:
    					if (!hasTouchPointers(id))
    						continue;
    					touchEvent.setAction(TouchEvent.ACTION_OUTSIDE);
    					for (int p : onTouchPointers.get(id)) {
    						if (pointerContains[ev.findPointerIndex(p)]) {
    							touchEvent.setAction(TouchEvent.ACTION_MOVE);
    							break;
    						}
    					}
    					if (touchEvent.getAction() == TouchEvent.ACTION_OUTSIDE)
    						for (int i = 0; i < newTouchPointers.length; i++)
    							newTouchPointers[i] = false;
    					break;
    				case MotionEvent.ACTION_CANCEL:
    					if (!hasTouchPointers(id))
    						continue;
    					touchEvent.setAction(TouchEvent.ACTION_CANCEL);
						for (int i = 0; i < newTouchPointers.length; i++)
							newTouchPointers[i] = false;
    					break;
    				case MotionEvent.ACTION_POINTER_DOWN:
    					if (!pointerContains[actionPointer])
    						continue;
    					if (hasTouchPointers(id)) {
    						touchEvent.setAction(TouchEvent.ACTION_POINTER_DOWN);
    						touchEvent.setActionPointer(ev.getPointerId(actionPointer));
    					} else
    						touchEvent.setAction(TouchEvent.ACTION_DOWN);
    					break;
    				case MotionEvent.ACTION_POINTER_UP:
    					if (!hasTouchPointer(id, ev.getPointerId(actionPointer)))
    						continue;
    					if (getTouchPointersCount(id) >  1) {
    						touchEvent.setAction(TouchEvent.ACTION_POINTER_UP);
    						touchEvent.setActionPointer(ev.getPointerId(actionPointer));
    					} else
    						touchEvent.setAction(TouchEvent.ACTION_UP);
    					newTouchPointers[actionPointer] = false;
    					break;
    				default:
    					continue;
    			}
    			
    			int[] screenLocation = new int[2];
    			view.getLocationOnScreen(screenLocation);

    			touchEvent.setEventTime(ev.getEventTime());
    			for (int p = 0; p < ev.getPointerCount(); p++)
    				if (pointerContains[p] || hasTouchPointer(id, ev.getPointerId(p)))
    					touchEvent.addCurrent(ev.getPointerId(p), new ScreenCoordinates(ev.getPointerId(p), ev.getX(p) - screenLocation[0], ev.getY(p) - screenLocation[1]));
    			for (int i = 0; i < ev.getHistorySize(); i++) {
    				for (int p = 0; p < ev.getPointerCount(); p++)
    					if (pointerContains[p] || hasTouchPointer(id, ev.getPointerId(p)))
    						touchEvent.addHistorical(ev.getPointerId(p), i, new ScreenCoordinates(ev.getPointerId(p), ev.getHistoricalX(p, i) - screenLocation[0], ev.getHistoricalY(p, i) - screenLocation[1]));
    				touchEvent.addHistoricalEventTime(i, ev.getHistoricalEventTime(i));
    			}
    			
    			int viewPointers = 0;
    			for (boolean bool : newTouchPointers)
    				if (bool)
    					viewPointers++;
    			int[] touchPointers = new int[viewPointers];
    			int i = 0;
    			for (int p = 0; p < newTouchPointers.length; p++) {
    				if (newTouchPointers[p]) {
    					touchPointers[i] = ev.getPointerId(p);
    					i++;
    				}
    			}
    			onTouchPointers.put(id, touchPointers);
    			
    			defaultImplementation = false;
    			
    			Intent intent = this.intent.setAction(MobileFeelService.TOUCH_EVENT);
    			intent.putExtra(MobileFeelService.ID, id);
    			intent.putExtra(MobileFeelService.MOTION_EVENT, touchEvent);
    			startService(intent);
    		}
    	}
    	
    	if (defaultImplementation)
			return super.dispatchTouchEvent(ev);
		return true;
    }
    
    private boolean hasTouchPointers(int id) {
    	if (onTouchPointers.get(id) != null && onTouchPointers.get(id).length > 0)
    		return true;
    	return false;
    }
    
    private int getTouchPointersCount(int id) {
    	if (hasTouchPointers(id))
    		return onTouchPointers.get(id).length;
    	return 0;
    }
     
    private boolean hasTouchPointer(int viewId, int pointerId) {
    	if (hasTouchPointers(viewId)) {
    		int[] pointers = onTouchPointers.get(viewId);
    		for (int p : pointers)
    			if (p == pointerId)
    				return true;
    	}
    	return false;
    }
    
    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
	@Override
	public boolean onLongClick(View view) {
        Intent i = this.intent.setAction(MobileFeelService.LONGCLICK_EVENT);
        i.putExtra(MobileFeelService.ID, view.getId());
        startService(i);
		return true;
	}

    /**
     * Refer to Android's documentation. <i>Derived classes <b>MUST</b> call
     * through to the superclass implementation of this method.</i>
     */
	@Override
	public void onClick(View view) {
        Intent i = this.intent.setAction(MobileFeelService.CLICK_EVENT);
        i.putExtra(MobileFeelService.ID, view.getId());
        startService(i);
	}
	
	private void unregisterAllEvents() {
		for(int id : onClick){
			View view = this.findViewById(id);
			if(view != null)
				view.setOnClickListener(null);
		}

		for(int id : onLongClick){
			View view = this.findViewById(id);
			if(view != null)
				view.setOnLongClickListener(null);
		}
			
		onClick = new ArrayList<Integer>();
		onLongClick = new ArrayList<Integer>();
		onTouch = new ArrayList<Integer>();
	}
		
	/**
	 * Send a Serializable object to the desktop server application. On the PC
	 * side, an {@link DeviceListener#onCustomDataReceived(AndroidDevice,
	 * String, Serializable)} event will be fired.
	 * 
	 * @param text a String defining the type of information being sent
	 * @param object the Serilizable object
	 */
	protected final void sendCustomData(String text, Serializable object){
        Intent i = this.intent.setAction(MobileFeelService.CUSTOM_DATA);
        i.putExtra(MobileFeelService.TEXT, text);
        i.putExtra(MobileFeelService.OBJECT, object);
		startService(i);
	}
	
}